1   /*
2    * Copyright (C) 2008 The Guava Authors
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.google.common.base;
18  
19  import com.google.common.annotations.GwtCompatible;
20  import com.google.common.annotations.GwtIncompatible;
21  import com.google.common.base.Joiner.MapJoiner;
22  import com.google.common.collect.ImmutableMap;
23  import com.google.common.collect.ImmutableMultimap;
24  import com.google.common.collect.ImmutableSet;
25  import com.google.common.collect.Lists;
26  import com.google.common.collect.Maps;
27  import com.google.common.testing.NullPointerTester;
28  
29  import junit.framework.AssertionFailedError;
30  import junit.framework.TestCase;
31  
32  import java.io.IOException;
33  import java.util.Arrays;
34  import java.util.Iterator;
35  import java.util.Map;
36  import java.util.Set;
37  
38  /**
39   * Unit test for {@link Joiner}.
40   *
41   * @author Kevin Bourrillion
42   */
43  @GwtCompatible(emulated = true)
44  public class JoinerTest extends TestCase {
45    private static final Joiner J = Joiner.on("-");
46  
47    // <Integer> needed to prevent warning :(
48    private static final Iterable<Integer> ITERABLE_ = Arrays.<Integer>asList();
49    private static final Iterable<Integer> ITERABLE_1 = Arrays.asList(1);
50    private static final Iterable<Integer> ITERABLE_12 = Arrays.asList(1, 2);
51    private static final Iterable<Integer> ITERABLE_123 = Arrays.asList(1, 2, 3);
52    private static final Iterable<Integer> ITERABLE_NULL = Arrays.asList((Integer) null);
53    private static final Iterable<Integer> ITERABLE_NULL_NULL
54        = Arrays.asList((Integer) null, null);
55    private static final Iterable<Integer> ITERABLE_NULL_1 = Arrays.asList(null, 1);
56    private static final Iterable<Integer> ITERABLE_1_NULL = Arrays.asList(1, null);
57    private static final Iterable<Integer> ITERABLE_1_NULL_2 = Arrays.asList(1, null, 2);
58    private static final Iterable<Integer> ITERABLE_FOUR_NULLS
59        = Arrays.asList((Integer) null, null, null, null);
60  
61    public void testNoSpecialNullBehavior() {
62      checkNoOutput(J, ITERABLE_);
63      checkResult(J, ITERABLE_1, "1");
64      checkResult(J, ITERABLE_12, "1-2");
65      checkResult(J, ITERABLE_123, "1-2-3");
66  
67      try {
68        J.join(ITERABLE_NULL);
69        fail();
70      } catch (NullPointerException expected) {
71      }
72      try {
73        J.join(ITERABLE_1_NULL_2);
74        fail();
75      } catch (NullPointerException expected) {
76      }
77  
78      try {
79        J.join(ITERABLE_NULL.iterator());
80        fail();
81      } catch (NullPointerException expected) {
82      }
83      try {
84        J.join(ITERABLE_1_NULL_2.iterator());
85        fail();
86      } catch (NullPointerException expected) {
87      }
88    }
89  
90    public void testOnCharOverride() {
91      Joiner onChar = Joiner.on('-');
92      checkNoOutput(onChar, ITERABLE_);
93      checkResult(onChar, ITERABLE_1, "1");
94      checkResult(onChar, ITERABLE_12, "1-2");
95      checkResult(onChar, ITERABLE_123, "1-2-3");
96    }
97  
98    public void testSkipNulls() {
99      Joiner skipNulls = J.skipNulls();
100     checkNoOutput(skipNulls, ITERABLE_);
101     checkNoOutput(skipNulls, ITERABLE_NULL);
102     checkNoOutput(skipNulls, ITERABLE_NULL_NULL);
103     checkNoOutput(skipNulls, ITERABLE_FOUR_NULLS);
104     checkResult(skipNulls, ITERABLE_1, "1");
105     checkResult(skipNulls, ITERABLE_12, "1-2");
106     checkResult(skipNulls, ITERABLE_123, "1-2-3");
107     checkResult(skipNulls, ITERABLE_NULL_1, "1");
108     checkResult(skipNulls, ITERABLE_1_NULL, "1");
109     checkResult(skipNulls, ITERABLE_1_NULL_2, "1-2");
110   }
111 
112   public void testUseForNull() {
113     Joiner zeroForNull = J.useForNull("0");
114     checkNoOutput(zeroForNull, ITERABLE_);
115     checkResult(zeroForNull, ITERABLE_1, "1");
116     checkResult(zeroForNull, ITERABLE_12, "1-2");
117     checkResult(zeroForNull, ITERABLE_123, "1-2-3");
118     checkResult(zeroForNull, ITERABLE_NULL, "0");
119     checkResult(zeroForNull, ITERABLE_NULL_NULL, "0-0");
120     checkResult(zeroForNull, ITERABLE_NULL_1, "0-1");
121     checkResult(zeroForNull, ITERABLE_1_NULL, "1-0");
122     checkResult(zeroForNull, ITERABLE_1_NULL_2, "1-0-2");
123     checkResult(zeroForNull, ITERABLE_FOUR_NULLS, "0-0-0-0");
124   }
125 
126   private static void checkNoOutput(Joiner joiner, Iterable<Integer> set) {
127     assertEquals("", joiner.join(set));
128     assertEquals("", joiner.join(set.iterator()));
129 
130     Object[] array = Lists.newArrayList(set).toArray(new Integer[0]);
131     assertEquals("", joiner.join(array));
132 
133     StringBuilder sb1FromIterable = new StringBuilder();
134     assertSame(sb1FromIterable, joiner.appendTo(sb1FromIterable, set));
135     assertEquals(0, sb1FromIterable.length());
136 
137     StringBuilder sb1FromIterator = new StringBuilder();
138     assertSame(sb1FromIterator, joiner.appendTo(sb1FromIterator, set));
139     assertEquals(0, sb1FromIterator.length());
140 
141     StringBuilder sb2 = new StringBuilder();
142     assertSame(sb2, joiner.appendTo(sb2, array));
143     assertEquals(0, sb2.length());
144 
145     try {
146       joiner.appendTo(NASTY_APPENDABLE, set);
147     } catch (IOException e) {
148       throw new AssertionError(e);
149     }
150 
151     try {
152       joiner.appendTo(NASTY_APPENDABLE, set.iterator());
153     } catch (IOException e) {
154       throw new AssertionError(e);
155     }
156 
157     try {
158       joiner.appendTo(NASTY_APPENDABLE, array);
159     } catch (IOException e) {
160       throw new AssertionError(e);
161     }
162   }
163 
164   private static final Appendable NASTY_APPENDABLE = new Appendable() {
165     @Override
166     public Appendable append(CharSequence csq) throws IOException {
167       throw new IOException();
168     }
169     @Override
170     public Appendable append(CharSequence csq, int start, int end) throws IOException {
171       throw new IOException();
172     }
173     @Override
174     public Appendable append(char c) throws IOException {
175       throw new IOException();
176     }
177   };
178 
179   private static void checkResult(Joiner joiner, Iterable<Integer> parts, String expected) {
180     assertEquals(expected, joiner.join(parts));
181     assertEquals(expected, joiner.join(parts.iterator()));
182 
183     StringBuilder sb1FromIterable = new StringBuilder().append('x');
184     joiner.appendTo(sb1FromIterable, parts);
185     assertEquals("x" + expected, sb1FromIterable.toString());
186 
187     StringBuilder sb1FromIterator = new StringBuilder().append('x');
188     joiner.appendTo(sb1FromIterator, parts.iterator());
189     assertEquals("x" + expected, sb1FromIterator.toString());
190 
191     Integer[] partsArray = Lists.newArrayList(parts).toArray(new Integer[0]);
192     assertEquals(expected, joiner.join(partsArray));
193 
194     StringBuilder sb2 = new StringBuilder().append('x');
195     joiner.appendTo(sb2, partsArray);
196     assertEquals("x" + expected, sb2.toString());
197 
198     int num = partsArray.length - 2;
199     if (num >= 0) {
200       Object[] rest = new Integer[num];
201       for (int i = 0; i < num; i++) {
202         rest[i] = partsArray[i + 2];
203       }
204 
205       assertEquals(expected, joiner.join(partsArray[0], partsArray[1], rest));
206 
207       StringBuilder sb3 = new StringBuilder().append('x');
208       joiner.appendTo(sb3, partsArray[0], partsArray[1], rest);
209       assertEquals("x" + expected, sb3.toString());
210     }
211   }
212 
213   public void test_useForNull_skipNulls() {
214     Joiner j = Joiner.on("x").useForNull("y");
215     try {
216       j = j.skipNulls();
217       fail();
218     } catch (UnsupportedOperationException expected) {
219     }
220   }
221 
222   public void test_skipNulls_useForNull() {
223     Joiner j = Joiner.on("x").skipNulls();
224     try {
225       j = j.useForNull("y");
226       fail();
227     } catch (UnsupportedOperationException expected) {
228     }
229   }
230 
231   public void test_useForNull_twice() {
232     Joiner j = Joiner.on("x").useForNull("y");
233     try {
234       j = j.useForNull("y");
235       fail();
236     } catch (UnsupportedOperationException expected) {
237     }
238   }
239 
240   public void testMap() {
241     MapJoiner j = Joiner.on(";").withKeyValueSeparator(":");
242     assertEquals("", j.join(ImmutableMap.of()));
243     assertEquals(":", j.join(ImmutableMap.of("", "")));
244 
245     Map<String, String> mapWithNulls = Maps.newLinkedHashMap();
246     mapWithNulls.put("a", null);
247     mapWithNulls.put(null, "b");
248 
249     try {
250       j.join(mapWithNulls);
251       fail();
252     } catch (NullPointerException expected) {
253     }
254 
255     assertEquals("a:00;00:b", j.useForNull("00").join(mapWithNulls));
256 
257     StringBuilder sb = new StringBuilder();
258     j.appendTo(sb, ImmutableMap.of(1, 2, 3, 4, 5, 6));
259     assertEquals("1:2;3:4;5:6", sb.toString());
260   }
261 
262   public void testEntries() {
263     MapJoiner j = Joiner.on(";").withKeyValueSeparator(":");
264     assertEquals("", j.join(ImmutableMultimap.of().entries()));
265     assertEquals("", j.join(ImmutableMultimap.of().entries().iterator()));
266     assertEquals(":", j.join(ImmutableMultimap.of("", "").entries()));
267     assertEquals(":", j.join(ImmutableMultimap.of("", "").entries().iterator()));
268     assertEquals("1:a;1:b", j.join(ImmutableMultimap.of("1", "a", "1", "b").entries()));
269     assertEquals("1:a;1:b", j.join(ImmutableMultimap.of("1", "a", "1", "b").entries().iterator()));
270 
271     Map<String, String> mapWithNulls = Maps.newLinkedHashMap();
272     mapWithNulls.put("a", null);
273     mapWithNulls.put(null, "b");
274     Set<Map.Entry<String, String>> entriesWithNulls = mapWithNulls.entrySet();
275 
276     try {
277       j.join(entriesWithNulls);
278       fail();
279     } catch (NullPointerException expected) {
280     }
281 
282     try {
283       j.join(entriesWithNulls.iterator());
284       fail();
285     } catch (NullPointerException expected) {
286     }
287 
288     assertEquals("a:00;00:b", j.useForNull("00").join(entriesWithNulls));
289     assertEquals("a:00;00:b", j.useForNull("00").join(entriesWithNulls.iterator()));
290 
291     StringBuilder sb1 = new StringBuilder();
292     j.appendTo(sb1, ImmutableMultimap.of(1, 2, 3, 4, 5, 6, 1, 3, 5, 10).entries());
293     assertEquals("1:2;1:3;3:4;5:6;5:10", sb1.toString());
294 
295     StringBuilder sb2 = new StringBuilder();
296     j.appendTo(sb2, ImmutableMultimap.of(1, 2, 3, 4, 5, 6, 1, 3, 5, 10).entries().iterator());
297     assertEquals("1:2;1:3;3:4;5:6;5:10", sb2.toString());
298   }
299 
300   @SuppressWarnings("ReturnValueIgnored") // testing for exception
301   public void test_skipNulls_onMap() {
302     Joiner j = Joiner.on(",").skipNulls();
303     try {
304       j.withKeyValueSeparator("/");
305       fail();
306     } catch (UnsupportedOperationException expected) {
307     }
308   }
309 
310   private static class DontStringMeBro implements CharSequence {
311     @Override
312     public int length() {
313       return 3;
314     }
315     @Override
316     public char charAt(int index) {
317       return "foo".charAt(index);
318     }
319     @Override
320     public CharSequence subSequence(int start, int end) {
321       return "foo".subSequence(start, end);
322     }
323     @Override public String toString() {
324       throw new AssertionFailedError("shouldn't be invoked");
325     }
326   }
327 
328   // Don't do this.
329   private static class IterableIterator implements Iterable<Integer>, Iterator<Integer> {
330     private static final ImmutableSet<Integer> INTEGERS = ImmutableSet.of(1, 2, 3, 4);
331     private final Iterator<Integer> iterator;
332     public IterableIterator() {
333       this.iterator = iterator();
334     }
335     @Override public Iterator<Integer> iterator() {
336       return INTEGERS.iterator();
337     }
338     @Override public boolean hasNext() {
339       return iterator.hasNext();
340     }
341     @Override public Integer next() {
342       return iterator.next();
343     }
344     @Override public void remove() {
345       iterator.remove();
346     }
347   }
348 
349   @GwtIncompatible("StringBuilder.append in GWT invokes Object.toString(), unlike the JRE version.")
350   public void testDontConvertCharSequenceToString() {
351     assertEquals("foo,foo", Joiner.on(",").join(
352         new DontStringMeBro(), new DontStringMeBro()));
353     assertEquals("foo,bar,foo", Joiner.on(",").useForNull("bar").join(
354         new DontStringMeBro(), null, new DontStringMeBro()));
355   }
356 
357   @GwtIncompatible("NullPointerTester")
358   public void testNullPointers() {
359     NullPointerTester tester = new NullPointerTester();
360     tester.testAllPublicStaticMethods(Joiner.class);
361     tester.testInstanceMethods(Joiner.on(","), NullPointerTester.Visibility.PACKAGE);
362     tester.testInstanceMethods(Joiner.on(",").skipNulls(), NullPointerTester.Visibility.PACKAGE);
363     tester.testInstanceMethods(
364         Joiner.on(",").useForNull("x"), NullPointerTester.Visibility.PACKAGE);
365     tester.testInstanceMethods(
366         Joiner.on(",").withKeyValueSeparator("="), NullPointerTester.Visibility.PACKAGE);
367   }
368 }